package no.sachmuld.methodcombination.api.adapter;

import no.sachmuld.methodcombination.impl.AbstractMethodInvocation;
import no.sachmuld.methodcombination.api.BehaviourChain;
import no.sachmuld.methodcombination.api.MethodInvocation;

/**
 *
 */
public abstract class Standard<B, R> {

    private BehaviourChain<B> chain;

    public Standard(BehaviourChain<B> chain) {
        this.chain = chain;
    }

    public R execute() {

        R result = new AbstractMethodInvocation<B, R>(chain.bottomUp(), null, null, null) {
            @Override
            protected R executeBehaviour(B behaviour) {
                return executeAround(behaviour, this);
            }

            @Override
            protected R atEnd() {
                return executeBeforePrimaryAfter();
            }
        }.execute();

        return result;
    }

    private R executeBeforePrimaryAfter() {

        for (B behaviour : chain.bottomUp()) {
            executeBefore(behaviour);
        }

        R result = new AbstractMethodInvocation<B, R>(chain.bottomUp(), null, null, null) {
            @Override
            protected R executeBehaviour(B behaviour) {
                return executePrimary(behaviour, this);
            }
        }.execute();

        for (B behaviour : chain.topDown()) {
            executeAfter(behaviour);
        }

        return result;
    }

    protected R executeAround(B behaviour, MethodInvocation<R> methodInvocation) {
        return methodInvocation.proceed();
    }
    protected void executeBefore(B behaviour) {
    }
    protected void executeAfter(B behaviour) {
    }
    protected abstract R executePrimary(B behaviour, MethodInvocation<R> methodInvocation);

}
